#include <sys/errno.h>
#include <assert.h>

#include "base/simapi.h"
#include "base/threads//PthreadInterfaceLock.h"
#include "spinlock.h"

NEW_DELETE_OPERATORS_CPP(PthreadInterfaceLock,1);

PthreadInterfaceLock::PthreadInterfaceLock () {
    init();
}

PthreadInterfaceLock::~PthreadInterfaceLock () {
    fini();
}

int PthreadInterfaceLock::mutex_lock (pthread_mutex_t* lock) {
    Sim_BeginLock();
    spin_lock((int*)&lock->PTHREAD_MUTEX_T_COUNT);
    Sim_EndLock();
    return 0;
}

int PthreadInterfaceLock::mutex_unlock (pthread_mutex_t* lock) {
    Sim_BeginLock();
    spin_unlock((int*)&lock->PTHREAD_MUTEX_T_COUNT);
    Sim_EndLock();
    return 0;
}

int PthreadInterfaceLock::mutex_trylock (pthread_mutex_t* lock) {
    Sim_BeginLock();
    int acquired = trylock((int*)&lock->PTHREAD_MUTEX_T_COUNT);
    if (acquired == 1) {
        Sim_EndLock();
        return 0;
    }
    Sim_EndLock();
    return EBUSY;
}

int PthreadInterfaceLock::rwlock_rdlock (volatile pthread_rwlock_t* lock) {
    Sim_BeginLock();
    do {
        // this is to reduce the contention and a possible live-lock to lock->access_lock
        while (true) {
            int writer = lock->PTHREAD_RWLOCK_T_WRITER;
            if (writer == -1) {
                break;
            }
        }

        spin_lock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
        if ((int)lock->PTHREAD_RWLOCK_T_WRITER == -1) {
            lock->PTHREAD_RWLOCK_T_READERS++;
            spin_unlock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
            Sim_EndLock();
            return 0;
        } 
        spin_unlock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
    } while (true);  
    Sim_EndLock();
    return 0;
}

int PthreadInterfaceLock::rwlock_wrlock (volatile pthread_rwlock_t* lock) {
    Sim_BeginLock();
    do {
        while (true) {
            int writer = lock->PTHREAD_RWLOCK_T_WRITER;
            if (writer == -1) {
                break;
            }
            int num_readers = lock->PTHREAD_RWLOCK_T_READERS;
            if (num_readers == 0) {
                break;
            }
        }
        
        
        spin_lock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
        if ((int)lock->PTHREAD_RWLOCK_T_WRITER == -1 && lock->PTHREAD_RWLOCK_T_READERS == 0) {
            lock->PTHREAD_RWLOCK_T_WRITER = pthread_self(); 
            spin_unlock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
            Sim_EndLock();
            return 0;
        } 
        spin_unlock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
    } while (true);  
    Sim_EndLock();
    return 0;
}

int PthreadInterfaceLock::rwlock_unlock (pthread_rwlock_t* lock) {
    Sim_BeginLock();
    spin_lock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
    if ((int)pthread_self() == lock->PTHREAD_RWLOCK_T_WRITER) {
        // the write lock will be released
        lock->PTHREAD_RWLOCK_T_WRITER = -1;
    } else {
        // one of the read locks will be released
        lock->PTHREAD_RWLOCK_T_READERS = lock->PTHREAD_RWLOCK_T_READERS - 1;
    }   
    spin_unlock((int*)&lock->PTHREAD_RWLOCK_T_LOCK);
    Sim_EndLock();
    return 0;
}

int PthreadInterfaceLock::key_create (pthread_key_t* key, void (*destructor)(void*)) {
    spin_lock(&key_lock);
    int result = PthreadInterface::key_create(key, destructor);
    spin_unlock(&key_lock);
    return result;
}

int PthreadInterfaceLock::cond_wait (pthread_cond_t* cond, pthread_mutex_t* lock) {
    PTHREAD_COND_T_INIT(cond);
    int* thread_count  = (int*)&cond->PTHREAD_COND_T_THREAD_COUNT;
    volatile int* flag = (int*)&cond->PTHREAD_COND_T_FLAG;
    int* count_lock    = (int*)&cond->PTHREAD_COND_T_COUNT_LOCK;

    *thread_count++;

    pthread_mutex_unlock(lock);
    while (true) {
        int f = *flag;
        if (f == 1) {
            break;
        }
    }

    spin_lock(count_lock);

    *thread_count--;
    if (*thread_count == 0) {
        *flag = 0;
    }
    spin_unlock(count_lock);
    pthread_mutex_lock(lock);
    return 0;
}

int PthreadInterfaceLock::barrier_wait (pthread_barrier_t* barrier) {
    Sim_BeginBarrier();

    int const num_threads = PTHREAD_BARRIER_T_NUM_THREADS(barrier);
    int const self = Sim_GetMyId();
    tree_barrier_t * const barrier_ptr = PTHREAD_BARRIER_T_BARRIER_PTR(barrier);

    int const goal = 1 - barrier_ptr[self].value;

    int round_mask = 3;
    while ((self & round_mask) == 0 && round_mask < (num_threads << 2)) {
      int const spacing = (round_mask + 1) >> 2;
      for (int i = 1; i <= 3 && self + i*spacing < num_threads; ++i) {
        while (barrier_ptr[self + i*spacing].value != goal) {
          // spin
        }
      }
      round_mask = (round_mask << 2) + 3;
    }

    barrier_ptr[self].value = goal;
    while (barrier_ptr[0].value != goal) {
      // spin
    }

    Sim_EndBarrier();
    return 0;
}
